package com.mrlv.redis.config;


import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.annotation.PropertyAccessor;
import com.mrlv.redis.select.SelectableRedisTemplate;
import com.mrlv.redis.utils.RedisUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.cache.CacheManager;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.cache.RedisCacheConfiguration;
import org.springframework.data.redis.cache.RedisCacheManager;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.connection.jedis.JedisConnectionFactory;
import org.springframework.data.redis.core.RedisOperations;
import org.springframework.data.redis.serializer.*;
import redis.clients.jedis.JedisPoolConfig;

import com.fasterxml.jackson.databind.ObjectMapper;

/**
 * 
* @ClassName: RedisAppConfig
* @Description: redis数据配置
* @author CW6333 liubo
* @date 2018年7月31日
* @version: V1.0
 */
@Configuration
@ConditionalOnClass(RedisOperations.class)
@EnableConfigurationProperties(RedisProperties.class)
@ConditionalOnProperty(prefix="redis", name = "hostName")
public class MrlvRedisAutoConfiguration {

    private final static Logger log = LoggerFactory.getLogger(MrlvRedisAutoConfiguration.class);
	
	@Autowired
	private RedisProperties redisProperties;
    /**
     * JedisPoolConfig 连接池
     * @return
     */
    @Bean
    public JedisPoolConfig jedisPoolConfig() {
        JedisPoolConfig jedisPoolConfig = new JedisPoolConfig();
        // 最大空闲数
        jedisPoolConfig.setMaxIdle(redisProperties.getMaxIdle());
        // 连接池的最大数据库连接数
        jedisPoolConfig.setMaxTotal(redisProperties.getMaxTotal());
        // 最大建立连接等待时间
        jedisPoolConfig.setMaxWaitMillis(redisProperties.getMaxWaitMillis());
        // 逐出连接的最小空闲时间 默认1800000毫秒(30分钟)
        jedisPoolConfig.setMinEvictableIdleTimeMillis(redisProperties.getMinEvictableIdleTimeMillis());
        // 每次逐出检查时 逐出的最大数目 如果为负数就是 : 1/abs(n), 默认3
        jedisPoolConfig.setNumTestsPerEvictionRun(redisProperties.getNumTestsPerEvictionRun());
        // 逐出扫描的时间间隔(毫秒) 如果为负数,则不运行逐出线程, 默认-1
        jedisPoolConfig.setTimeBetweenEvictionRunsMillis(redisProperties.getTimeBetweenEvictionRunsMillis());
        // 是否在从池中取出连接前进行检验,如果检验失败,则从池中去除连接并尝试取出另一个
        jedisPoolConfig.setTestOnBorrow(redisProperties.isTestOnBorrow());
        // 在空闲时检查有效性, 默认false
        jedisPoolConfig.setTestWhileIdle(redisProperties.isTestWhileIdle());
        log.info("Redis连接池创建完成");
        return jedisPoolConfig;
    }

    /**
     * 单机版配置
     * @author Mrlv
     * @date 2021/12/19 16:23
     * @param jedisPoolConfig
     * @return org.springframework.data.redis.connection.jedis.JedisConnectionFactory
     */
    @Bean
    public JedisConnectionFactory JedisConnectionFactory(JedisPoolConfig jedisPoolConfig){
        JedisConnectionFactory JedisConnectionFactory = new JedisConnectionFactory(jedisPoolConfig);
        //连接池
        JedisConnectionFactory.setPoolConfig(jedisPoolConfig);  
        //IP地址
        JedisConnectionFactory.setHostName(redisProperties.getHostName());
        //如果Redis设置有密码
        JedisConnectionFactory.setPassword(redisProperties.getPassword());
        //端口号
        JedisConnectionFactory.setPort(redisProperties.getPort());
        //客户端超时时间单位是毫秒  
        JedisConnectionFactory.setTimeout(redisProperties.getTimeout());
        return JedisConnectionFactory; 
    }


//    @Bean
//    public CacheManager cacheManager(RedisTemplate<String, Object> redisTemplate) {
//        RedisCacheManager cacheManager = new RedisCacheManager(redisTemplate);
//        //设置缓存过期时间
//        cacheManager.setDefaultExpiration(10000);
//        return cacheManager;
//    }

    /**
     * 缓存管理器
     * @author Mrlv
     * @date 2021/12/19 16:23
     * @param factory
     * @return org.springframework.cache.CacheManager
     */
    @Bean
    public CacheManager cacheManager(RedisConnectionFactory factory) {
        RedisSerializer<String> redisSerializer = new StringRedisSerializer();
        Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer(Object.class);

        //解决查询缓存转换异常的问题
        ObjectMapper om = new ObjectMapper();
        om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
        om.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
        jackson2JsonRedisSerializer.setObjectMapper(om);

        // 配置序列化（解决乱码的问题）
        RedisCacheConfiguration config = RedisCacheConfiguration.defaultCacheConfig()
                .serializeKeysWith(RedisSerializationContext.SerializationPair.fromSerializer(redisSerializer))
                .serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(jackson2JsonRedisSerializer))
                .disableCachingNullValues();

        RedisCacheManager cacheManager = RedisCacheManager.builder(factory)
                .cacheDefaults(config)
                .build();
        return cacheManager;
    }

    /**
     * 在 set 和 get 过程中，添加重写序列化和反序列化的方法
     * ConditionalOnMissingBean:它是修饰bean的一个注解，主要实现的是，当你的bean被注册之后，如果而注册相同类型的bean，就不会成功，
     * 它会保证你的bean只有一个，即你的实例只有一个，当你注册多个相同的bean时，会出现异常
     * @author Mrlv
     * @date 2021/12/19 16:25
     * @return com.mrlv.redis.config.MrlvRedisStringSerializer
     */
    @Bean(name = "redisMyStringSerializer")
    @ConditionalOnMissingBean(RedisSerializer.class)
    public MrlvRedisStringSerializer getRedisMyStringSerializer(){
       return  new MrlvRedisStringSerializer();
    }


    /**
     * 实例化RedisTemplate对象
     * @return
     */
    @Bean
    @ConditionalOnMissingBean
    public SelectableRedisTemplate<String, Object> functionDomainRedisTemplate(RedisConnectionFactory redisConnectionFactory, RedisSerializer redisMyStringSerializer) {
        SelectableRedisTemplate<String, Object> redisTemplate = new SelectableRedisTemplate<String, Object>();
        GenericJackson2JsonRedisSerializer genericJackson2JsonRedisSerializer = new GenericJackson2JsonRedisSerializer();
        //如果不配置Serializer，那么存储的时候缺省使用String，如果用User类型存储，那么会提示错误User can't cast to String！
        redisTemplate.setKeySerializer(redisMyStringSerializer);
        redisTemplate.setValueSerializer(genericJackson2JsonRedisSerializer);
        redisTemplate.setHashKeySerializer(redisMyStringSerializer);
        redisTemplate.setHashValueSerializer(genericJackson2JsonRedisSerializer);
        redisTemplate.setStringSerializer(new StringRedisSerializer());
        redisTemplate.setExposeConnection(true);
        // 开启事务
        // redisTemplate.setEnableTransactionSupport(true);
        redisTemplate.setConnectionFactory(redisConnectionFactory);
        redisTemplate.afterPropertiesSet();
        log.info("实例化 RedisTemplate 对象完成");
        return redisTemplate;
    }

    /**
     * 通过注入方式生成Redis工具类，注入封装RedisTemplate
     * @author Mrlv
     * @date 2021/12/19 16:24
     * @param redisTemplate
     * @return com.mrlv.redis.utils.RedisUtils
     */
    @Bean(name = "redisUtils")
    @ConditionalOnMissingBean
    public RedisUtils redisUtil(SelectableRedisTemplate<String, Object> redisTemplate) {
        RedisUtils redisUtil = new RedisUtils();
        redisUtil.setRedisTemplate(redisTemplate);
        log.info("注入封装RedisTemplatec完成");
        return redisUtil;
    }
}
